package com.ctrip.corp.obt.shard.sql.mybatis.interceptor.compatibility;

import com.ctrip.corp.obt.generic.utils.CollectionUtils;
import com.ctrip.corp.obt.generic.utils.ReflectionUtils;
import com.ctrip.corp.obt.generic.utils.StringUtils;
import com.ctrip.corp.obt.shard.sql.mybatis.interceptor.compatibility.constant.CompatibilityConstant;
import com.ctrip.corp.obt.shard.sql.parser.AbstractSqlParserSupport;
import com.ctrip.corp.obt.shard.utils.PluginUtils;
import com.google.common.collect.Lists;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import org.apache.ibatis.mapping.ParameterMapping;
import org.springframework.data.util.Pair;

import javax.persistence.Id;
import java.lang.reflect.Field;
import java.sql.JDBCType;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static com.ctrip.corp.obt.shard.sql.mybatis.interceptor.compatibility.constant.CompatibilityConstant.PARAMETER_OBJECT_KEY_PREFIX;
import static com.ctrip.corp.obt.shard.sql.mybatis.interceptor.compatibility.constant.CompatibilityConstant.PRIMARY_KEY_ID;

/**
 * 描述
 *
 * @author x.shen
 * @since 2024/4/29
 */
public abstract class BaseSemanticCompatibility extends AbstractSqlParserSupport {

    /**
     * bit类型中的true，false
     */
    private static final Set<String> BIT_BOOLEAN_TRUE =
        new HashSet<>(Lists.newArrayList("true", "1", "bit'1'", "b'1'"));
    private static final Set<String> BIT_BOOLEAN_FALSE =
        new HashSet<>(Lists.newArrayList("false", "0", "bit'0'", "b'0'"));
    /**
     * bit类型兼容前缀
     */
    private static final String BIT_TYPE_COMPATIBILITY_FALSE = "'0'";
    private static final String BIT_TYPE_COMPATIBILITY_TRUE = "'1'";

    /**
     * 处理引号
     * mysql: 双引号表示字符串常量，反引号表示列名
     * kingbase/dm: 单引号表示字符串常量，双引号表示列名
     *
     * @param sql
     * @return
     */
    public String handleQuotes(String sql) {
        return sql.replace("\"", "'").replace("`", "\"");
    }

    /**
     * 处理别名，将别名中所有的引号去掉，变成无引号形式
     * 
     * @param alias
     */
    public void handleAsAlias(Alias alias) {
        if (Objects.isNull(alias) || StringUtils.isBlank(alias.getName())) {
            return;
        }

        if (alias.getName().contains("\"") || alias.getName().contains("'")) {
            alias.setName(alias.getName().replace("\"", "").replace("'", ""));
        }
    }

    /**
     * 处理boolean转bit
     *
     * @param leftExpression
     * @param rightExpression
     * @param columnWithTypeMap
     */
    public void handleColumnType(Column leftExpression, Expression rightExpression,
        Map<String, String> columnWithTypeMap) {

        if (CollectionUtils.isEmpty(columnWithTypeMap)) {
            return;
        }

        if (columnWithTypeMap.containsKey(leftExpression.getColumnName())) {
            String type = columnWithTypeMap.get(leftExpression.getColumnName());
            // 处理bit类型 BIT
            if (JDBCType.BIT.getName().equalsIgnoreCase(type)) {
                handleBitColumn(rightExpression);
            } else if (JDBCType.TIMESTAMP.getName().equalsIgnoreCase(type)) {
                // 处理timestamp类型
                handleTimeStampColumn(rightExpression);
            }
        }
    }

    /**
     * 处理bit类型兼容方案，
     * true/1 ==> '1'
     * false/0 ==> '0'
     *
     * @param rightExpression
     */
    protected void handleBitColumn(Expression rightExpression) {
        String rightValue = rightExpression.toString().toLowerCase();
        if (!BIT_BOOLEAN_TRUE.contains(rightValue) && !BIT_BOOLEAN_FALSE.contains(rightValue)) {
            return;
        }

        // is_delete = false
        String newBitColumnValue = null;
        if (rightExpression instanceof Column) {
            Column rightColumn = (Column)rightExpression;
            newBitColumnValue = handleBitColumn(rightColumn.getColumnName().toLowerCase());
            if (StringUtils.isNotBlank(newBitColumnValue)) {
                rightColumn.setColumnName(newBitColumnValue);
            }
        } else if (rightExpression instanceof LongValue) {
            // is_delete = 0
            LongValue rightLongValue = (LongValue)rightExpression;
            newBitColumnValue = handleBitColumn(rightLongValue.getStringValue().toLowerCase());
            if (StringUtils.isNotBlank(newBitColumnValue)) {
                rightLongValue.setStringValue(newBitColumnValue);
            }
        } else if (rightExpression instanceof StringValue) {
            // is_delete = "0" || is_delete = b'0'
            StringValue rightStringValue = (StringValue)rightExpression;
            newBitColumnValue = handleBitColumn(rightStringValue.getValue().toLowerCase());
            if (StringUtils.isNotBlank(newBitColumnValue)) {
                // bit,b 都是prefix
                rightStringValue.setPrefix(null);
                // 对于string需要直接使用0，不能使用'0'，不然后续会出现 ''0'' 这种情况
                rightStringValue.setValue(newBitColumnValue.substring(1, 2));
            }
        }
    }

    private String handleBitColumn(String columnValue) {
        if (BIT_BOOLEAN_TRUE.contains(columnValue)) {
            return BIT_TYPE_COMPATIBILITY_TRUE;
        } else if (BIT_BOOLEAN_FALSE.contains(columnValue)) {
            return BIT_TYPE_COMPATIBILITY_FALSE;
        }
        return null;
    }

    /**
     * 处理关键字
     * 
     * @param expression
     * @param keyWords
     */
    public void handleKeyWords(Expression expression, Set<String> keyWords) {
        if (!keyWords.contains(expression.toString())) {
            return;
        }

        if (expression instanceof Column) {
            Column column = (Column)expression;
            column.setColumnName("\"" + column.getColumnName() + "\"");
        }
    }

    /**
     * 处理时间格式类型
     * delete_time = 'Thu Jan 01 08:00:00 CST 1970' ===> delete_time = '1970-01-01 08:00:00'
     *
     * @param rightExpression
     */
    private void handleTimeStampColumn(Expression rightExpression) {
        // 时间格式一般为string
        if(!(rightExpression instanceof StringValue)){
            return;
        }

        StringValue timeStamp = (StringValue) rightExpression;

        // 待兼容日期格式
        String originalFormat = "EEE MMM dd HH:mm:ss zzz yyyy";
        // 判断是否是需要兼容的时间格式
        if (!isValidDateFormat(originalFormat, timeStamp.getValue())) {
            return;
        }

        // 目标日期格式
        String targetFormat = "yyyy-MM-dd HH:mm:ss";
        try {
            Date parse = new SimpleDateFormat(originalFormat,Locale.US).parse(timeStamp.getValue());
            // 设置解析的时间格式
            timeStamp.setValue(new SimpleDateFormat(targetFormat).format(parse));
        } catch (ParseException e) {
            // do nothing
            log.error(String.format("信创数据库时间格式%s转换失败：%s", rightExpression, e));
        }
    }

    /**
     * 判断是否是对应的时间格式
     * 
     * @param dateString
     * @return
     */
    public boolean isValidDateFormat(String pattern, String dateString) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, Locale.US);
        try {
            dateFormat.parse(dateString);
            return true; // 解析成功，说明符合格式
        } catch (ParseException e) {
            return false; // 解析失败，说明不符合格式
        }
    }

    // ******************************************* insert 语法兼容 *******************************************//

    /**
     * insert 主键处理，当主键id为null时，执行删除id字段的操作
     * 目前下列方法无法处理子类重写id字段的情况，如：mb_role
     * 按照最简单的逻辑处理，只认为当前所有自增主键指定字段都是id
     *
     * @param insert
     * @param boundSql
     */
    public void handleInsertPrimaryKey(Insert insert, PluginUtils.DSBoundSql boundSql) {
        // 获取插入列
        List<Column> columns = insert.getColumns();
        Set<String> columnNameSet = columns.stream()
            .map(column -> column.getColumnName().toLowerCase())
            .collect(Collectors.toSet());
        // 当列中不包含id时，忽略
        if (!columnNameSet.contains(PRIMARY_KEY_ID)) {
            return;
        }

        Pair<String, Integer> pair = null;
        /*
        当使用xml时，获取的是一个Map(好像)
        获取一个Map的参数集合，其中键的个数是入参的两倍：
        1.@Param(...)中的别名作为键，若没有别名，则以arg1，arg2作为别名。
        2.以param1，param2按照顺序作为键，与1中顺序对应。
         */
        Map<?, ?> parameterObjectMap = null;
        if (boundSql.parameterObject() instanceof Map<?, ?>) {
            parameterObjectMap = (Map<?, ?>)boundSql.parameterObject();
            if (CollectionUtils.isEmpty(parameterObjectMap)) {
                return;
            }
            int keySet = parameterObjectMap.size();

            // 根据param1、param2获取
            for (int i = 1; i <= (keySet / 2); i++) {
                String key = PARAMETER_OBJECT_KEY_PREFIX + i;
                if (!parameterObjectMap.containsKey(key)) {
                    // 当使用mybatis自带的insertList时，获得的map信息key为 arg0，collection，list这三个
                    key = "collection";
                }
                Object parameterObject = parameterObjectMap.get(key);
                if (parameterObject instanceof List<?>) {
                    // 多个对象
                    for (Object parameter : (List<?>)parameterObject) {
                        pair = handleParameterObject(parameter, columns);
                        if (StringUtils.isNotBlank(pair.getFirst())) {
                            break;
                        }
                    }
                } else {
                    // 单一对象
                    pair = handleParameterObject(parameterObject, columns);
                }

                if (Objects.nonNull(pair) && StringUtils.isNotBlank(pair.getFirst())) {
                    boundSql
                        .parameterMappings(handlePrimaryKey(pair, insert.getItemsList(), boundSql.parameterMappings()));
                    break;
                }
            }
        } else {
            // 非map类型，使用mybatis自带的insert语句时
            // 单一对象
            pair = handleParameterObject(boundSql.parameterObject(), columns);
            if (StringUtils.isNotBlank(pair.getFirst())) {
                boundSql.parameterMappings(handlePrimaryKey(pair, insert.getItemsList(), boundSql.parameterMappings()));
            }
        }
    }

    /**
     * 以列表形式做批量插入，目前只考虑列表中包含只包含entity对象
     *
     * @param parameter
     * @param columns
     * @return
     */
    private Pair<String, Integer> handleParameterObject(Object parameter, List<Column> columns) {
        // 主键名称
        String primaryName = "";
        // 主键索引位
        Integer primaryIndex = -1;

        Iterator<Column> iterator = columns.iterator();
        while (iterator.hasNext()) {
            Column next = iterator.next();
            String columnName = convertToCamelCase(next.getColumnName()).toLowerCase();

            // 只认为id为主键
            if (!PRIMARY_KEY_ID.equals(columnName)) {
                continue;
            }

            // 判断是否存在主键注解，子类 及其 父类
            if (whetherHasPrimaryKey(parameter.getClass(), columnName, parameter)
                || whetherHasPrimaryKey(parameter.getClass().getSuperclass(), columnName, parameter)) {
                primaryName = next.getColumnName();
                primaryIndex++;
                iterator.remove();
                break;
            }
        }

        return Pair.of(primaryName, primaryIndex);
    }

    /**
     * 决定是否需要处理“?”
     *
     * @param itemsList
     * @return
     */
    private List<ParameterMapping> handlePrimaryKey(Pair<String, Integer> pair, ItemsList itemsList,
        List<ParameterMapping> parameterMappings) {

        String primaryName = pair.getFirst();
        int primaryIndex = pair.getSecond();
        if (itemsList instanceof MultiExpressionList) {
            // list.size() > 1
            MultiExpressionList multiExpressionList = (MultiExpressionList)itemsList;
            List<ExpressionList> expressionLists = multiExpressionList.getExpressionLists();
            for (ExpressionList expressionList : expressionLists) {
                // 防止误移除元素
                expressionList.getExpressions().remove(primaryIndex);
            }
        } else if (itemsList instanceof ExpressionList) {
            // list.size() = 1 或 原entity
            ExpressionList expressionList = (ExpressionList)itemsList;
            expressionList.getExpressions().remove(primaryIndex);
        }
        // 全量删除
        return parameterMappings.stream()
            .filter(parameterMapping -> !parameterMapping.getProperty()
                .substring(parameterMapping.getProperty().lastIndexOf(".") + 1).equals(primaryName))
            .collect(Collectors.toList());

    }

    /**
     * 使用反射的方式发现主键注解，注意需要检查当前类以及其父类，因为存在当前子类会重写父类中的id的情况（子类不加@Id注解）
     *
     * @param parameter
     * @param columnName
     * @return
     */
    private boolean whetherHasPrimaryKey(Class<?> clazz, String columnName, Object parameter) {
        // 判断是否存在主键注解
        Field field = ReflectionUtils.findField(clazz, columnName);
        if (Objects.isNull(field)) {
            return false;
        }
        try {
            ReflectionUtils.makeAccessible(field);
            // (引入javax包) 存在主键 && 主键为null
            return Objects.nonNull(field.getAnnotation(Id.class))
                && Objects.isNull(ReflectionUtils.getField(field, parameter));
        } catch (IllegalStateException e) {
            log.error(e.getMessage());
        }
        return false;
    }

    /**
     * 改为驼峰命名，以便从实体类中找到对应字段
     *
     * @param columnName
     * @return
     */
    private String convertToCamelCase(String columnName) {
        if (!columnName.contains("_")) {
            return columnName;
        }
        String[] parts = columnName.split("_");
        return Arrays.stream(parts, 1, parts.length)
            .map(s -> s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase())
            .reduce(parts[0].toLowerCase(), String::concat);
    }

    /**
     * 将needAddColumnList添加至selectItemsList中
     * 
     * @param needAddColumnList
     * @param selectItemsList
     */
    public void handleOrderByWithDistinct(List<Column> needAddColumnList, List<SelectItem> selectItemsList) {
        selectItemsList.addAll(needAddColumnList.stream().map(SelectExpressionItem::new).collect(Collectors.toList()));
    }
}
